#ifndef SPARSE_MATRIX_H
#define SPARSE_MATRIX_H

#include "Constants.h"
#include "DataTypes.h"
#include "MatrixEntry.h"

#include <fstream>
#include <iostream>
#include <limits.h>
#include <list>
#include <stdio.h>

using namespace std;

template <typename T>
class SparseMatrix
{
    UINT _nrows;  /* number of rows in the matrix */
    UINT _ncols;  /* number of columns in the matrix */
    ULONG _nterms; /* number of non-zero terms in the matrix */
    MatrixEntry<T> **_entries; /* collection of matrix entries */

    /**
     * @param nrows number of rows in the current matrix.
     */
    void setRows(UINT nrows);

    /**
     * @param ncols number of columns in the current matrix.
     */
    void setCols(UINT ncols);

    /**
     * @param nterms number of matrix entries in the current matrix.
     * nterms should be lesser than or equal to nrows & ncols
     */
    void setTerms(ULONG nterms);

    /**
     *
     * @param a first matrix to be added.
     * @param b second matrix to be added
     * @return  number of non zero entries in the matrix formed by adding
     * matrices a and b.
     */
    static ULONG count_add_entries(SparseMatrix<T> &a, SparseMatrix<T> &b);

    /**
     * Appends the array add_me to end of the current sparse matrix.
     * 
     * @param add_me a collection of matrix entries to be added and the end of
     * the current matrix.
     * @param nterms number of terms to be added. It refers to the length of the
     * add_me array length.
     * @param copy indicates whether shallow / deep copy should be taken of each
     * entry of add_me
     * 
     * @return TRUE if append operation was successful, FALSE otherwise.
     */
    BOOL append(MatrixEntry<T> *add_me, ULONG nterms, BOOL copy = FALSE);

    /**
     * Assigns the array add_me to the current sparse matrix.
     *
     * @param add_me a collection of matrix entries to be assigned starting at
     * the index passed in.
     * @param si start index within the current matrix array.
     * @param nterms number of terms to be added. It refers to the length of the
     * add_me array length.
     * @param copy indicates whether shallow / deep copy should be taken of each
     * entry of add_me
     *
     * @return TRUE if assign operation was successful, FALSE otherwise.
     */
    BOOL assign(MatrixEntry<T> *add_me, ULONG si,
                ULONG nterms, BOOL copy = FALSE);

    /**
     * @param new_size the new size of the current matrix.
     *
     * @return TRUE if resize was successful, FALSE otherwise.
     *
     * @warning the new size passed in should be strictly greater than the
     * number of entries in this matrix.
     */
    BOOL resize(ULONG new_size);
    
public:
    /**
     *
     * @param nrows  : number of rows in the matrix.
     * @param ncols  : number of clumns in the matrix.
     * @param nterms : number of valid terms
     */
    explicit SparseMatrix(UINT nrows, UINT ncols, ULONG nterms);

    /**
     * Removes the store of matrix entries.
     * number of terms, rows & columns are reset to 0.
     */
    virtual ~SparseMatrix();

    /**
     * prints the matrix entries.
     * Starts of by printing the number of rows and colums and then proceeds to
     * print the matrix entries, rowsize.
     */
    void print();


    /**
     * Reads the file in the specified format and initialized the sparce matrix.
     * Format of the file:
     * NUMBER_OF_ROWS NUMBER_OF_COLS NUMBER_OF_ENTRIES
     * ENTRY1_ROW ENTRY1_COL ENTRY1_VALUE
     * ENTRY2_ROW ENTRY2_COL ENTRY2_VALUE
     * .
     * .
     * .
     * ENTRYN_ROW ENTRYN_COL ENTRYN_VALUE
     * @param filename
     *
     * @return a new sparse matrix if the initization was successful.
     *
     * @throws MatrixException if there is an irrecoverable problem in read.
     */
    static SparseMatrix<T> *init(const char *filename);

    /**
     * transposes the given sparse matrix and places the transpose in put_here.
     * @param dst : holds the transpose of the current sparse matrix instance.
     *
     * @return TRUE if the transpose operation was successful. FALSE otherwise.
     */
    BOOL transpose(SparseMatrix<T> *dst);

    /**
     * @return number of rows.
     */
    UINT getRows();

    /**
     * @return number of colums.
     */
    UINT getCols();

    /**
     * @return number of matrix entries stored. The number of terms should be
     * lesser than or equal to getRows() * getCols()
     */
    ULONG getTerms();

    /**
     * Adds the current sparse matrix to the matrix passed in as the parameter and
     * returns a new matrix.
     * If both matrices should have the same number of rows and columns to be
     * addable.
     *
     * @param add_me matrix to be added.
     *
     * @return a new sparse matrix with (*this) + add_ me.
     *
     * @throws MatrixException if rows and columns in the matrices do not match.
     * or there is a problem allocating memory.
     */
    SparseMatrix<T> *operator+(SparseMatrix<T> &add_me);

    /**
     * Refer operator+. The idea here is to preallocate space for the terms.
     * If either of the matrices have an entry, then, the resultant matrix will
     * contain that entry.
     * Adds the current sparse matrix to the matrix passed in as the parameter and
     * return a new matrix.
     * If both matrices should have the same number of rows and columns to be
     * addable.
     * 
     * @return a new sparse matrix with (*this) + add_ me.
     *
     * @throws MatrixException if rows and columns in the matrices do not match.
     * or there is a problem allocating memory.
     */
    SparseMatrix<T> *faster_add(SparseMatrix<T> &add_me);

    /**
     * Multiplies the current sparse matrix to the matrix passed in as the 
     * parameter and returns a new matrix.
     * If number of colums of this matrix does not match number of
     * rows in b, multiplication is not possible.
     *
     * @param b matrix to be multiplied.
     *
     * @return a new sparse matrix with (*this) * b
     *
     * @throws MatrixException if rows and columns in the matrices do not match.
     * or there is a problem allocating memory.
     */
    SparseMatrix<T> *operator*(SparseMatrix<T> &b);
};

#endif
